#include "connection_pool.h"
#include "../utils/logger.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <signal.h>

// Utility function to get current time in microseconds
static uint64_t get_time_microseconds(void) {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec;
}

// Create connection pool
connection_pool_t* connection_pool_create(const connection_pool_config_t* config, 
                                         gudb* database) {
    if (!config || !database) {
        return NULL;
    }
    
    connection_pool_t* pool = calloc(1, sizeof(connection_pool_t));
    if (!pool) {
        return NULL;
    }
    
    // Copy configuration
    memcpy(&pool->config, config, sizeof(connection_pool_config_t));
    pool->database = database;
    
    // Allocate connection slots
    pool->total_slots = config->max_connections;
    pool->connections = calloc(pool->total_slots, sizeof(pool_connection_t*));
    if (!pool->connections) {
        free(pool);
        return NULL;
    }
    
    // Initialize synchronization primitives
    if (pthread_mutex_init(&pool->pool_mutex, NULL) != 0 ||
        pthread_cond_init(&pool->pool_cond, NULL) != 0 ||
        pthread_rwlock_init(&pool->stats_lock, NULL) != 0) {
        free(pool->connections);
        free(pool);
        return NULL;
    }
    
    pool->start_time = time(NULL);
    pool->initialized = false;
    pool->shutting_down = false;
    pool->threads_running = false;
    
    return pool;
}

// Initialize connection pool
int connection_pool_init(connection_pool_t* pool) {
    if (!pool || pool->initialized) {
        return -1;
    }
    
    pthread_mutex_lock(&pool->pool_mutex);
    
    // Pre-allocate initial connections
    for (uint32_t i = 0; i < pool->config.initial_connections; i++) {
        pool_connection_t* conn = calloc(1, sizeof(pool_connection_t));
        if (!conn) {
            pthread_mutex_unlock(&pool->pool_mutex);
            return -1;
        }
        
        // Initialize connection mutex
        if (pthread_mutex_init(&conn->conn_mutex, NULL) != 0) {
            free(conn);
            pthread_mutex_unlock(&pool->pool_mutex);
            return -1;
        }
        
        conn->pool_state = POOL_CONN_STATE_FREE;
        conn->created_time = time(NULL);
        conn->last_used_time = conn->created_time;
        conn->pool_slot_id = i;
        conn->mysql_conn = NULL; // Will be assigned when socket accepted
        
        // Add to free list
        if (!pool->free_list_head) {
            pool->free_list_head = pool->free_list_tail = conn;
        } else {
            pool->free_list_tail->next = conn;
            conn->prev = pool->free_list_tail;
            pool->free_list_tail = conn;
        }
        
        pool->connections[i] = conn;
        pool->free_count++;
        pool->used_slots++;
    }
    
    pool->initialized = true;
    
    pthread_mutex_unlock(&pool->pool_mutex);
    
    LOG_INFO("CONNECTION_POOL", 
             "Connection pool initialized: max=%u, initial=%u, min=%u",
             pool->config.max_connections,
             pool->config.initial_connections,
             pool->config.min_connections);
    
    return 0;
}

// Destroy connection pool
void connection_pool_destroy(connection_pool_t* pool) {
    if (!pool) {
        return;
    }
    
    LOG_INFO("CONNECTION_POOL", "Shutting down connection pool");
    
    // Signal shutdown
    pthread_mutex_lock(&pool->pool_mutex);
    pool->shutting_down = true;
    pthread_cond_broadcast(&pool->pool_cond);
    pthread_mutex_unlock(&pool->pool_mutex);
    
    // Stop background threads
    connection_pool_stop_background_threads(pool);
    
    // Close all connections
    pthread_mutex_lock(&pool->pool_mutex);
    
    for (uint32_t i = 0; i < pool->used_slots; i++) {
        pool_connection_t* conn = pool->connections[i];
        if (conn) {
            if (conn->mysql_conn) {
                mysql_connection_destroy(conn->mysql_conn);
            }
            pthread_mutex_destroy(&conn->conn_mutex);
            free(conn);
        }
    }
    
    // Clean up request queue
    connection_request_t* req = pool->request_queue_head;
    while (req) {
        connection_request_t* next = req->next;
        pthread_cond_destroy(&req->wait_cond);
        pthread_mutex_destroy(&req->wait_mutex);
        free(req);
        req = next;
    }
    
    pthread_mutex_unlock(&pool->pool_mutex);
    
    // Clean up synchronization primitives
    pthread_mutex_destroy(&pool->pool_mutex);
    pthread_cond_destroy(&pool->pool_cond);
    pthread_rwlock_destroy(&pool->stats_lock);
    
    // Free memory
    free(pool->connections);
    free(pool);
    
    LOG_INFO("CONNECTION_POOL", "Connection pool destroyed");
}

// Start background threads
int connection_pool_start_background_threads(connection_pool_t* pool) {
    if (!pool || pool->threads_running) {
        return -1;
    }
    
    pool->threads_running = true;
    
    // Start cleanup thread
    if (pthread_create(&pool->cleanup_thread, NULL, 
                      connection_pool_cleanup_thread_func, pool) != 0) {
        pool->threads_running = false;
        return -1;
    }
    
    // Start monitor thread
    if (pthread_create(&pool->monitor_thread, NULL, 
                      connection_pool_monitor_thread_func, pool) != 0) {
        pool->threads_running = false;
        pthread_cancel(pool->cleanup_thread);
        return -1;
    }
    
    LOG_INFO("CONNECTION_POOL", "Background threads started");
    return 0;
}

// Stop background threads
int connection_pool_stop_background_threads(connection_pool_t* pool) {
    if (!pool || !pool->threads_running) {
        return -1;
    }
    
    pool->threads_running = false;
    
    // Cancel and join threads
    pthread_cancel(pool->cleanup_thread);
    pthread_cancel(pool->monitor_thread);
    
    pthread_join(pool->cleanup_thread, NULL);
    pthread_join(pool->monitor_thread, NULL);
    
    LOG_INFO("CONNECTION_POOL", "Background threads stopped");
    return 0;
}

// Acquire connection from pool
pool_connection_t* connection_pool_acquire(connection_pool_t* pool, 
                                          uint32_t timeout_sec) {
    if (!pool || !pool->initialized || pool->shutting_down) {
        return NULL;
    }
    
    uint64_t start_time = get_time_microseconds();
    pool_connection_t* conn = NULL;
    
    pthread_mutex_lock(&pool->pool_mutex);
    
    // Update statistics
    pool->stats.connection_requests++;
    
    // Try to get connection from free list
    if (pool->free_list_head) {
        conn = pool->free_list_head;
        
        // Remove from free list
        pool->free_list_head = conn->next;
        if (pool->free_list_head) {
            pool->free_list_head->prev = NULL;
        } else {
            pool->free_list_tail = NULL;
        }
        
        conn->next = conn->prev = NULL;
        pool->free_count--;
        
        // Add to active list
        if (!pool->active_list_head) {
            pool->active_list_head = pool->active_list_tail = conn;
        } else {
            pool->active_list_tail->next = conn;
            conn->prev = pool->active_list_tail;
            pool->active_list_tail = conn;
        }
        
        conn->pool_state = POOL_CONN_STATE_ACTIVE;
        conn->last_used_time = time(NULL);
        pool->active_count++;
        pool->stats.connection_pool_hits++;
        
        pthread_mutex_unlock(&pool->pool_mutex);
        
        // Update performance monitor
        POOL_PERF_CONNECTION_CREATED(pool);
        
        uint64_t wait_time = get_time_microseconds() - start_time;
        pthread_rwlock_wrlock(&pool->stats_lock);
        pool->stats.avg_wait_time_ms = 
            (pool->stats.avg_wait_time_ms + (wait_time / 1000.0)) / 2.0;
        pthread_rwlock_unlock(&pool->stats_lock);
        
        return conn;
    }
    
    // No free connections available, try to create new one
    if (pool->used_slots < pool->total_slots) {
        conn = calloc(1, sizeof(pool_connection_t));
        if (conn && pthread_mutex_init(&conn->conn_mutex, NULL) == 0) {
            conn->pool_state = POOL_CONN_STATE_ACTIVE;
            conn->created_time = time(NULL);
            conn->last_used_time = conn->created_time;
            conn->pool_slot_id = pool->used_slots;
            
            // Add to active list
            if (!pool->active_list_head) {
                pool->active_list_head = pool->active_list_tail = conn;
            } else {
                pool->active_list_tail->next = conn;
                conn->prev = pool->active_list_tail;
                pool->active_list_tail = conn;
            }
            
            pool->connections[pool->used_slots] = conn;
            pool->used_slots++;
            pool->active_count++;
            pool->stats.connection_pool_misses++;
            pool->stats.total_connections_created++;
            
            if (pool->active_count > pool->stats.peak_connections) {
                pool->stats.peak_connections = pool->active_count;
            }
            
            pthread_mutex_unlock(&pool->pool_mutex);
            
            POOL_PERF_CONNECTION_CREATED(pool);
            
            return conn;
        }
        
        if (conn) {
            free(conn);
        }
    }
    
    // Pool is full, need to wait or return NULL
    if (timeout_sec == 0) {
        pthread_mutex_unlock(&pool->pool_mutex);
        pool->stats.connection_wait_timeouts++;
        return NULL;
    }
    
    // Create connection request and add to queue
    connection_request_t* request = calloc(1, sizeof(connection_request_t));
    if (!request) {
        pthread_mutex_unlock(&pool->pool_mutex);
        return NULL;
    }
    
    pthread_mutex_init(&request->wait_mutex, NULL);
    pthread_cond_init(&request->wait_cond, NULL);
    request->request_time = time(NULL);
    request->timeout_sec = timeout_sec;
    request->completed = false;
    request->timed_out = false;
    
    // Add to request queue
    if (!pool->request_queue_head) {
        pool->request_queue_head = pool->request_queue_tail = request;
    } else {
        pool->request_queue_tail->next = request;
        pool->request_queue_tail = request;
    }
    pool->queue_size++;
    
    pthread_mutex_unlock(&pool->pool_mutex);
    
    // Wait for connection with timeout
    pthread_mutex_lock(&request->wait_mutex);
    
    struct timespec timeout_ts;
    clock_gettime(CLOCK_REALTIME, &timeout_ts);
    timeout_ts.tv_sec += timeout_sec;
    
    int wait_result = 0;
    while (!request->completed && !request->timed_out && wait_result == 0) {
        wait_result = pthread_cond_timedwait(&request->wait_cond, 
                                           &request->wait_mutex, &timeout_ts);
    }
    
    if (wait_result == ETIMEDOUT) {
        request->timed_out = true;
        pool->stats.connection_wait_timeouts++;
    }
    
    conn = request->assigned_conn;
    
    pthread_mutex_unlock(&request->wait_mutex);
    pthread_mutex_destroy(&request->wait_mutex);
    pthread_cond_destroy(&request->wait_cond);
    free(request);
    
    return conn;
}

// Release connection back to pool
int connection_pool_release(connection_pool_t* pool, pool_connection_t* conn) {
    if (!pool || !conn) {
        return -1;
    }
    
    pthread_mutex_lock(&pool->pool_mutex);
    pthread_mutex_lock(&conn->conn_mutex);
    
    // Check if connection should be recycled
    if (connection_pool_should_recycle_connection(conn, pool->config.max_requests_per_conn) ||
        conn->marked_for_cleanup) {
        
        pthread_mutex_unlock(&conn->conn_mutex);
        int result = connection_pool_close_connection(pool, conn);
        pthread_mutex_unlock(&pool->pool_mutex);
        return result;
    }
    
    // Remove from active list
    if (conn->prev) {
        conn->prev->next = conn->next;
    } else {
        pool->active_list_head = conn->next;
    }
    
    if (conn->next) {
        conn->next->prev = conn->prev;
    } else {
        pool->active_list_tail = conn->prev;
    }
    
    pool->active_count--;
    
    // Reset connection state
    conn->pool_state = POOL_CONN_STATE_FREE;
    conn->last_used_time = time(NULL);
    conn->next = conn->prev = NULL;
    
    // Add to free list
    if (!pool->free_list_head) {
        pool->free_list_head = pool->free_list_tail = conn;
    } else {
        pool->free_list_tail->next = conn;
        conn->prev = pool->free_list_tail;
        pool->free_list_tail = conn;
    }
    pool->free_count++;
    
    pthread_mutex_unlock(&conn->conn_mutex);
    
    // Process waiting requests
    if (pool->request_queue_head) {
        connection_request_t* request = pool->request_queue_head;
        pool->request_queue_head = request->next;
        if (!pool->request_queue_head) {
            pool->request_queue_tail = NULL;
        }
        pool->queue_size--;
        
        // Assign connection to request
        request->assigned_conn = conn;
        request->completed = true;
        
        pthread_cond_signal(&request->wait_cond);
    } else {
        // Signal that a connection is available
        pthread_cond_signal(&pool->pool_cond);
    }
    
    pthread_mutex_unlock(&pool->pool_mutex);
    
    POOL_PERF_CONNECTION_DESTROYED(pool);
    
    return 0;
}

// Close connection
int connection_pool_close_connection(connection_pool_t* pool, pool_connection_t* conn) {
    if (!pool || !conn) {
        return -1;
    }
    
    // Remove from active list
    if (conn->prev) {
        conn->prev->next = conn->next;
    } else {
        pool->active_list_head = conn->next;
    }
    
    if (conn->next) {
        conn->next->prev = conn->prev;
    } else {
        pool->active_list_tail = conn->prev;
    }
    
    pool->active_count--;
    
    // Close underlying MySQL connection
    if (conn->mysql_conn) {
        mysql_connection_destroy(conn->mysql_conn);
        conn->mysql_conn = NULL;
    }
    
    // Update statistics
    pool->stats.total_connections_destroyed++;
    
    // Calculate connection lifetime
    time_t lifetime = time(NULL) - conn->created_time;
    pool->stats.avg_connection_lifetime_sec = 
        (pool->stats.avg_connection_lifetime_sec + lifetime) / 2.0;
    
    // Mark slot as available for reuse
    conn->pool_state = POOL_CONN_STATE_FREE;
    conn->mysql_conn = NULL;
    conn->request_count = 0;
    conn->marked_for_cleanup = false;
    
    // Add back to free list for reuse
    conn->next = conn->prev = NULL;
    if (!pool->free_list_head) {
        pool->free_list_head = pool->free_list_tail = conn;
    } else {
        pool->free_list_tail->next = conn;
        conn->prev = pool->free_list_tail;
        pool->free_list_tail = conn;
    }
    pool->free_count++;
    
    POOL_PERF_CONNECTION_DESTROYED(pool);
    
    return 0;
}

// Create connection for incoming socket
pool_connection_t* connection_pool_create_connection(connection_pool_t* pool, 
                                                    int sockfd, 
                                                    struct sockaddr_in* client_addr) {
    if (!pool || sockfd < 0 || !client_addr) {
        return NULL;
    }
    
    // Acquire connection from pool
    pool_connection_t* conn = connection_pool_acquire(pool, 5); // 5 second timeout
    if (!conn) {
        return NULL;
    }
    
    pthread_mutex_lock(&conn->conn_mutex);
    
    // Create underlying MySQL connection
    conn->mysql_conn = mysql_connection_create(sockfd, client_addr);
    if (!conn->mysql_conn) {
        pthread_mutex_unlock(&conn->conn_mutex);
        connection_pool_release(pool, conn);
        return NULL;
    }
    
    // Initialize connection buffers
    if (mysql_connection_init_buffers(conn->mysql_conn, 
                                     64 * 1024, 64 * 1024) != 0) {
        mysql_connection_destroy(conn->mysql_conn);
        conn->mysql_conn = NULL;
        pthread_mutex_unlock(&conn->conn_mutex);
        connection_pool_release(pool, conn);
        return NULL;
    }
    
    conn->pool_state = POOL_CONN_STATE_ACTIVE;
    conn->last_used_time = time(NULL);
    
    pthread_mutex_unlock(&conn->conn_mutex);
    
    LOG_DEBUG("CONNECTION_POOL", "Created connection for socket %d", sockfd);
    
    return conn;
}

// Check if connection should be recycled
bool connection_pool_should_recycle_connection(pool_connection_t* conn, 
                                              uint32_t max_requests) {
    if (!conn) {
        return false;
    }
    
    return conn->request_count >= max_requests;
}

// Check if connection is expired
bool connection_pool_is_connection_expired(pool_connection_t* conn, 
                                          uint32_t timeout_sec) {
    if (!conn) {
        return true;
    }
    
    time_t now = time(NULL);
    return (now - conn->last_used_time) > timeout_sec;
}

// Cleanup idle connections
int connection_pool_cleanup_idle_connections(connection_pool_t* pool) {
    if (!pool) {
        return -1;
    }
    
    pthread_mutex_lock(&pool->pool_mutex);
    
    uint32_t cleaned_up = 0;
    pool_connection_t* conn = pool->free_list_head;
    
    while (conn && pool->free_count > pool->config.min_connections) {
        pool_connection_t* next = conn->next;
        
        if (connection_pool_is_connection_expired(conn, pool->config.connection_timeout_sec)) {
            // Remove from free list
            if (conn->prev) {
                conn->prev->next = conn->next;
            } else {
                pool->free_list_head = conn->next;
            }
            
            if (conn->next) {
                conn->next->prev = conn->prev;
            } else {
                pool->free_list_tail = conn->prev;
            }
            
            pool->free_count--;
            
            // Close underlying connection
            if (conn->mysql_conn) {
                mysql_connection_destroy(conn->mysql_conn);
                conn->mysql_conn = NULL;
            }
            
            // Keep the pool_connection_t structure for reuse
            conn->pool_state = POOL_CONN_STATE_FREE;
            conn->next = conn->prev = NULL;
            
            cleaned_up++;
        }
        
        conn = next;
    }
    
    pool->stats.cleanup_operations++;
    pool->stats.last_cleanup_time = time(NULL);
    
    pthread_mutex_unlock(&pool->pool_mutex);
    
    if (cleaned_up > 0) {
        LOG_DEBUG("CONNECTION_POOL", "Cleaned up %u idle connections", cleaned_up);
    }
    
    return cleaned_up;
}

// Get pool statistics
int connection_pool_get_stats(connection_pool_t* pool, connection_pool_stats_t* stats) {
    if (!pool || !stats) {
        return -1;
    }
    
    pthread_rwlock_rdlock(&pool->stats_lock);
    memcpy(stats, &pool->stats, sizeof(connection_pool_stats_t));
    pthread_rwlock_unlock(&pool->stats_lock);
    
    return 0;
}

// Update pool statistics
int connection_pool_update_stats(connection_pool_t* pool) {
    if (!pool) {
        return -1;
    }
    
    pthread_rwlock_wrlock(&pool->stats_lock);
    pthread_mutex_lock(&pool->pool_mutex);
    
    pool->stats.current_active_connections = pool->active_count;
    pool->stats.current_idle_connections = pool->free_count;
    pool->last_stats_update = time(NULL);
    
    pthread_mutex_unlock(&pool->pool_mutex);
    pthread_rwlock_unlock(&pool->stats_lock);
    
    POOL_PERF_UPDATE_METRICS(pool);
    
    return 0;
}

// Print pool statistics
void connection_pool_print_stats(connection_pool_t* pool) {
    if (!pool) {
        return;
    }
    
    connection_pool_stats_t stats;
    if (connection_pool_get_stats(pool, &stats) != 0) {
        return;
    }
    
    LOG_INFO("CONNECTION_POOL_STATS", 
             "Active: %llu, Idle: %llu, Peak: %llu, Created: %llu, "
             "Destroyed: %llu, Requests: %llu, Hits: %llu, Misses: %llu, "
             "Timeouts: %llu, Avg Lifetime: %.2f sec, Avg Wait: %.2f ms",
             stats.current_active_connections,
             stats.current_idle_connections,
             stats.peak_connections,
             stats.total_connections_created,
             stats.total_connections_destroyed,
             stats.connection_requests,
             stats.connection_pool_hits,
             stats.connection_pool_misses,
             stats.connection_wait_timeouts,
             stats.avg_connection_lifetime_sec,
             stats.avg_wait_time_ms);
}

// Cleanup thread function
void* connection_pool_cleanup_thread_func(void* arg) {
    connection_pool_t* pool = (connection_pool_t*)arg;
    if (!pool) {
        return NULL;
    }
    
    LOG_DEBUG("CONNECTION_POOL", "Cleanup thread started");
    
    while (pool->threads_running && !pool->shutting_down) {
        sleep(pool->config.cleanup_interval_sec);
        
        if (!pool->threads_running || pool->shutting_down) {
            break;
        }
        
        connection_pool_cleanup_idle_connections(pool);
        connection_pool_update_stats(pool);
    }
    
    LOG_DEBUG("CONNECTION_POOL", "Cleanup thread stopped");
    return NULL;
}

// Monitor thread function
void* connection_pool_monitor_thread_func(void* arg) {
    connection_pool_t* pool = (connection_pool_t*)arg;
    if (!pool) {
        return NULL;
    }
    
    LOG_DEBUG("CONNECTION_POOL", "Monitor thread started");
    
    while (pool->threads_running && !pool->shutting_down) {
        sleep(30); // Monitor every 30 seconds
        
        if (!pool->threads_running || pool->shutting_down) {
            break;
        }
        
        connection_pool_print_stats(pool);
        
        // Check pool health
        if (!connection_pool_is_healthy(pool)) {
            LOG_WARN("CONNECTION_POOL", "Pool health check failed");
        }
    }
    
    LOG_DEBUG("CONNECTION_POOL", "Monitor thread stopped");
    return NULL;
}

// Check if pool is healthy
bool connection_pool_is_healthy(connection_pool_t* pool) {
    if (!pool) {
        return false;
    }
    
    pthread_mutex_lock(&pool->pool_mutex);
    
    bool healthy = true;
    
    // Check if we have too many active connections
    if (pool->active_count > pool->config.max_connections * 0.9) {
        healthy = false;
    }
    
    // Check if we have too many waiting requests
    if (pool->queue_size > pool->config.max_queue_size * 0.8) {
        healthy = false;
    }
    
    pthread_mutex_unlock(&pool->pool_mutex);
    
    return healthy;
}

// Get active connection count
uint32_t connection_pool_get_active_count(connection_pool_t* pool) {
    if (!pool) {
        return 0;
    }
    
    pthread_mutex_lock(&pool->pool_mutex);
    uint32_t count = pool->active_count;
    pthread_mutex_unlock(&pool->pool_mutex);
    
    return count;
}

// Get free connection count
uint32_t connection_pool_get_free_count(connection_pool_t* pool) {
    if (!pool) {
        return 0;
    }
    
    pthread_mutex_lock(&pool->pool_mutex);
    uint32_t count = pool->free_count;
    pthread_mutex_unlock(&pool->pool_mutex);
    
    return count;
}